module AutoBase
  class CommandLine
    MandatoryStart = "<"
    MandatoryStop = ">"
    OptionalStart = "["
    OptionalStop = "]"
    Argument = "%"
  
    # command spec
    class Spec < Array
      attr_accessor :type
    end
  
    class NotMandatoryError < RuntimeError; end
  
    class SpecParser
      def initialize(words, args)
        @args = args
        @words = words
      end
  
      def parse
        spec = Spec.new
        spec.type = true
      
        atom = ""
        while word = @words[0]
          case word
          when CommandLine::MandatoryStart
            @words.shift
          
            ssp = SubSpecParser.new(
              CommandLine::MandatoryStop, @words, @args
            ).parse
            unless replace(spec, atom, ssp)
              raise CommandLine::NotMandatoryError.new(atom)
            end
          
            atom = ""
          
          when CommandLine::OptionalStart
            @words.shift
            ssp = SubSpecParser.new(
              CommandLine::OptionalStop, @words, @args
            ).parse
            unless replace(spec, atom, ssp)
              raise CommandLine::NotMandatoryError.new(atom)
            end
            atom = ""
          when CommandLine::MandatoryStop
            return spec
          when CommandLine::OptionalStop
            return spec
          else
            @words.shift
            atom << word
          end
        end
      
        if atom.size > 0
          unless replace(spec, atom)
            raise CommandLine::NotMandatoryError.new(atom)
          end
        end
      
        return spec
      end
    
      def asub(atom)
        arr = atom.split(CommandLine::Argument)
        tmp = []
        arr.each_with_index {|ea, ind|
          if 1 == ind % 2
            if @args && @args[ea.to_sym]
              tmp << @args[ea.to_sym].to_s
            else
              return nil
            end
          else
            tmp << ea
          end
        }
      
        tmp.join("")
      end
    
      def replace(spec, atom, sspe = nil)
        if atom.size > 0
          subs = asub(atom)
          if subs
            spec << subs
          else
            return false
          end
        end
        spec << sspe if sspe && sspe.size > 0
        return true
      end
    end

    class SubSpecParser
      def initialize(sym, words, args)
        @sym, @words, @args = sym, words, args
      end
  
      def parse
        spec = Spec.new
        if @sym == CommandLine::MandatoryStop
          spec.type = true
        else
          spec.type = false
        end
      
        atom = ""
        while word = @words.shift
          case word
          when CommandLine::MandatoryStart
            ssp = SubSpecParser.new(
              CommandLine::MandatoryStop, @words, @args
            ).parse
            unless replace(spec, atom, ssp)
              raise CommandLine::NotMandatoryError.new(atom)
            end
          
            atom = ""
          when CommandLine::OptionalStart
            ssp = SubSpecParser.new(
              CommandLine::OptionalStop, @words, @args
            ).parse
            unless replace(spec, atom, ssp)
              return ""
            end
          
            atom = ""
          when @sym
            spec << asub(atom) if atom.size > 0
            spar = SpecParser.new(@words, @args).parse
            if spar.size > 0
              spec << spec
            end
            return spec
          else
            atom << word
          end
        end
        return spec
      end
    
      def asub(atom)
        arr = atom.split(CommandLine::Argument)
        tmp = []
        arr.each_with_index {|ea, ind|
          if 1 == ind % 2
            if @args[ea.to_sym]
              tmp << @args[ea.to_sym].to_s
            else
              return nil
            end
          else
            tmp << ea
          end
        }
      
        tmp.join("")
      end
    
      def replace(spec, atom, sspe)
        if atom.size > 0
          subs = asub(atom)
          if subs
            spec << subs
          else
            return false
          end
        end
        spec << sspe if sspe.size > 0
        return true
      end
    end
  
    def spec(cmd_spec)
      @spec = cmd_spec
    end
  
    def args(cmd_args = {})
      @args = cmd_args
    end
  
    def cmd
      sp = SpecParser.new(@spec.split(""), @args)
      sp.parse.flatten.join("").strip
    end
  end
end